<?php
/**
 * User: wanghousheng
 * Date: 2017/6/14
 * Time: 14:29
 * 微信支付api
 */

class Wxpay
{
    //支付请求url
    private $wx_pay_url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
    private $notify_url = '';//异步回调地址(绝对)
    public $appid = '';
    public $mchid = '';
    public $key = '';
    public $secret = '';
    public $attach = '-';//自定义值
    public function __construct($params=array())
    {
        if (isset($params['APPID']) && $params['APPID']) $this->appid = trim($params['APPID']);
        if (isset($params['MCHID']) && $params['MCHID']) $this->mchid = trim($params['MCHID']);
        if (isset($params['KEY']) && $params['KEY']) $this->key = trim($params['KEY']);
        if (isset($params['ATTACH']) && $params['ATTACH']) $this->attach = trim($params['ATTACH']);
        if (isset($params['APPSECRET']) && $params['APPSECRET']) $this->secret = trim($params['APPSECRET']);
        if (isset($params['BACkURL']) && $params['BACkURL']) $this->notify_url = $params['BACkURL'];
        if (empty($this->appid) || empty($this->mchid) || empty($this->key) || empty($this->notify_url)){
            return $this->ApiState(false,'配置信息错误');
        }
    }

    /**
     * Name: 扫码支付
     * User: wanghousheng
     * Date: 2017/6/14
     * @param $order_name 订单名称
     * @param $order_money 订单价格
     * @param $order_sn 订单编号
     * @return string 需要生成二维码的URL
     */
    public function CodePay($order_name,$order_money,$order_sn){
        if ($order_name && $order_money && $order_sn){
            $return['appid'] = $this->appid;
            $return['mch_id'] = $this->mchid;
            $return['device_info'] = 'WEB';
            $return['nonce_str'] =$this->BuildStr();
            $return['sign_type'] = 'MD5';
            $return['trade_type'] = 'NATIVE';
            $return['notify_url'] =$this->notify_url;
            $return['product_id'] = trim($order_sn);
            $return['spbill_create_ip'] = \Xiaoc\Api\getIp();
            $return['out_trade_no'] = trim($order_sn);
            $return['total_fee'] = $order_money*100;
            $return['body'] =trim($order_name);
            $return['attach'] = $this->attach;//自定义的值
            $return['sign'] = $this->MakeSign($return,$this->key);
            $xml = $this->BuidXml($return);
            $data = $this->postXmlCurl($xml,$this->wx_pay_url);
            $result = $this->FromXml($data);
            if ($result['return_code']=='SUCCESS' && $result['result_code']=='SUCCESS'){
                if (isset($result['code_url']) && $result['code_url']) {
                    return $this->ApiState(true,'success',array('url'=>$result['code_url']));
                }
            }
            return $this->ApiState(false,$result);
        }else{
            return $this->ApiState(false,'参数错误');
        }
    }

    /**
     * Name: App支付
     * User: wanghousheng
     * Date: 2017/6/14
     * @param $order_name 订单名称
     * @param $order_money 订单价格
     * @param $order_sn 订单编号
     * @return array app请求支付所需参数
     */
    public function AppPay($order_name,$order_money,$order_sn){
        $return['appid'] = $this->appid;
        $return['mch_id'] = $this->mchid;
        $return['device_info'] = 'WEB';
        $return['nonce_str'] =$this->BuildStr();
        $return['sign_type'] = 'MD5';
        $return['trade_type'] = 'APP';
        $return['notify_url'] =$this->notify_url;
        $return['product_id'] = trim($order_sn);
        $return['spbill_create_ip'] = \Xiaoc\Api\getIp();
        $return['out_trade_no'] = trim($order_sn);
        $return['total_fee'] = $order_money*100;
        $return['body'] =trim($order_name);
        $return['attach'] = $this->attach;//自定义的值
        $return['sign'] = $this->MakeSign($return,$this->key);
        $xml = $this->BuidXml($return);
        $data = $this->postXmlCurl($xml,$this->wx_pay_url);
        $result = $this->FromXml($data);
        if ($result['return_code']=='SUCCESS' && $result['result_code']=='SUCCESS'){
            $res['apiKey'] = $this->key;
            $res['orderId'] = $result['prepay_id'];
            $res['mchId'] = $result['mch_id'];
            $res['nonceStr'] = $this->BuildStr();
            $res['timeStamp'] = time();
            $sign['appid'] = $this->appid;
            $sign['partnerid'] = $this->mchid;
            $sign['prepayid'] = $result['prepay_id'];
            $sign['package'] = 'Sign=WXPay';
            $sign['noncestr'] = $res['nonceStr'];
            $sign['timestamp'] = $res['timeStamp'];
            $res['sign'] = $this->MakeSign($sign,$this->key);
            return $this->ApiState(true,'success',$res);
        }
        return $this->ApiState(false,$result);
    }
    /**
     * Name: JSAPI支付
     * User: wanghousheng
     * Date: 2017/6/14
     * @param $order_name 订单名称
     * @param $order_money 订单价格
     * @param $order_sn 订单编号
     * @param $successUrl 支付成功之后的url(绝对路径)
     * @param $errorUrl 支付失败的url(绝对路径)
     */
    public function JspPay($order_name,$order_money,$order_sn,$successUrl,$errorUrl){
        if ($order_name && $order_money && $order_sn && $successUrl && $errorUrl){
            $openId = $this->getOpenId();
            $return['appid'] = $this->appid;
            $return['mch_id'] = $this->mchid;
            $return['device_info'] = 'WEB';
            $return['nonce_str'] =$this->BuildStr();
            $return['sign_type'] = 'MD5';
            $return['trade_type'] = 'JSAPI';
            $return['notify_url'] =$this->notify_url;
            $return['product_id'] = trim($order_sn);
            $return['spbill_create_ip'] = \Xiaoc\Api\getIp();
            $return['out_trade_no'] = trim($order_sn);
            $return['total_fee'] = $order_money*100;
            $return['body'] =trim($order_name);
            $return['attach'] = $this->attach;//自定义的值
            $return['openid'] = $openId;
            $return['sign'] = $this->MakeSign($return,$this->key);
            $xml = $this->BuidXml($return);
            $data = $this->postXmlCurl($xml,$this->wx_pay_url);
            $result = $this->FromXml($data);
            if ($result['return_code'] != 'SUCCESS'){
                return  $this->ApiState(false,$result['return_msg']);
            }
            $this->paymentHtml($result['prepay_id'],$successUrl,$errorUrl);
        }


    }
    private function getOpenId()
    {
        if (empty($_GET['code'])) {
            if (isset($_GET['state']) && $_GET['state'] == 'redirected') {
                return $this->ApiState(false,'授权失败');
            }
            $url = sprintf(
                'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=redirected#wechat_redirect',
                $this->appid,
                urlencode('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'])
            );
            header('Location: ' . $url);
            exit;
        }
        $rurl = sprintf(
            'https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code',
            $this->appid,
            $this->secret,
            $_GET['code']
        );
        //$d = json_decode(file_get_contents($rurl), true);
        $res = $this->wxcurl_get($rurl);
        $d = json_decode($res, true);
        if (empty($d)) {
            return $this->ApiState(false,'请求失败');
        }
        if (empty($d['errcode']) && isset($d['openid'])) {
            return $d['openid'];
        }
        return $this->ApiState(false,$d);
    }

    private function wxcurl_get($url){
        $chs = curl_init();
        curl_setopt($chs, CURLOPT_TIMEOUT, 30);
        curl_setopt($chs, CURLOPT_URL, $url);
        curl_setopt($chs, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($chs, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($chs, CURLOPT_HEADER, FALSE);
        curl_setopt($chs, CURLOPT_RETURNTRANSFER, TRUE);
        $response = curl_exec($chs);
        if (!$response) {
            throw new Exception('CURL Error: ' . curl_errno($chs));
        }
        curl_close($chs);
        return $response;
    }
    /**
     *
     * 取得订单支付状态，成功或失败
     * @param array $param
     * @return array
     */
    public function getPayResult(){
        $param = $this->FromXml(file_get_contents('php://input'));
        if($param['return_code']=='SUCCESS' && $param['transaction_id'] && $param['result_code']=='SUCCESS')
        {
            return $this->ApiState(true,'success',array('out_trade_no'=>$param['out_trade_no'],'transaction_id'=>$param['transaction_id']));
        }
        return $this->ApiState(false,'error',$param);
    }
    private function ToUrlParams($data)
    {
        $buff = "";
        foreach ($data as $k => $v)
        {
            if($k != "sign" && $v != "" && !is_array($v)){
                $buff .= $k . "=" . $v . "&";
            }
        }
        $buff = trim($buff, "&");
        return $buff;
    }

    /**
     * 生成签名
     * @return 签名，本函数不覆盖sign成员变量，如要设置签名需要调用SetSign方法赋值
     */
    private function MakeSign($data,$key)
    {
        //签名步骤一：按字典序排序参数
        ksort($data);
        $string = $this->ToUrlParams($data);
        //签名步骤二：在string后加入KEY
        $string = $string . "&key=".$key;
        //签名步骤三：MD5加密
        $string = md5($string);
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 随机字符
     * return 随机字符串
     */
    private function BuildStr()
    {
        $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        $noceStr = "";
        for ($i = 0; $i < 32; $i++) {
            $noceStr .= $chars[ mt_rand(0, strlen($chars) - 1) ];
        }
        //把最终的字符串签名，获得签名结果
        return $noceStr;
    }
    //数组转化为xml
    public function BuidXml($data){
        $xml = "<xml>";
        foreach ($data as $key=>$val)
        {
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }
    //postxml
    private function postXmlCurl($xml,$url,$second = 30)
    {
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch,CURLOPT_URL, $url);
        curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
        curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);//严格校验2
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            die("curl出错，错误码:$error");
            $this->ApiState(false,"curl出错，错误码:$error");
        }
    }
    //xml转化为数组
    public function FromXml($xml)
    {
        libxml_disable_entity_loader(true);
        $data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $data;
    }
    /**
     * Name: 返回状态
     * User: wanghousheng
     * Date: 2017/6/14
     * @param bool $status
     * @param string $msg
     * @param array $data
     * @return array
     */
    private function ApiState($status = true,$msg = 'success',$data = array()){
        return array('status'=>$status,'message'=>$msg,'data'=>$data);
    }

    /**
     * Name: 生成支付页面
     * User: wanghousheng
     * Date: 2017/6/14
     * @param $prepayId
     * @param $successUrl
     * @param $errorUrl
     * @return string
     */
    private function paymentHtml($prepayId,$successUrl,$errorUrl)
    {
        $jsApiObj["appId"] = $this->appid;
        $timeStamp = time();
        $jsApiObj["timeStamp"] = "$timeStamp";
        $jsApiObj["nonceStr"] = $this->BuildStr();
        $jsApiObj["package"] = "prepay_id=".$prepayId;
        $jsApiObj["signType"] = "MD5";
        $jsApiObj["paySign"] = $this->MakeSign($jsApiObj,$this->key);
        $jsonParams = json_encode($jsApiObj);
        return <<<EOB
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=utf-8" />
<title>微信支付</title>
</head>
<body>
正在加载…
<script type="text/javascript">
function jsApiCall() {
    WeixinJSBridge.invoke(
        'getBrandWCPayRequest',
        {$jsonParams},
        function(res) {
            WeixinJSBridge.log(res.err_msg);
            var h;
            if (res.err_msg == "get_brand_wcpay_request:ok") {
                // success;
                h = '{$successUrl}';
            } else {
                // fail;
                alert(res && res.err_msg);
                h = '{$errorUrl}';
            }
            location.href = h.replace('_attach_', '{$this->attach}');
        }
    );
}
if (typeof WeixinJSBridge == "undefined"){
		    if( document.addEventListener ){
		        document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);
		    }else if (document.attachEvent){
		        document.attachEvent('WeixinJSBridgeReady', jsApiCall); 
		        document.attachEvent('onWeixinJSBridgeReady', jsApiCall);
		    }
		}else{
		   wx.ready(function () {
		    jsApiCall();
		    });
		    
		}
</script>
</body>
</html>
EOB;
    }

}